package cn.jjxx.shiro.session.mgt;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import com.google.common.collect.Sets;

import cn.jjxx.core.security.shiro.session.SessionDAO;
import cn.jjxx.core.utils.DateUtils; 
import cn.jjxx.core.utils.ServletUtils;
import cn.jjxx.core.utils.StringUtils;
import cn.jjxx.modules.sys.utils.UserUtils;
import cn.jjxx.shiro.session.RedisUtil;
import cn.jjxx.shiro.session.config.RedisConfig; 

/**
 * 指定shiro的sessionManager使用指定的
 * 存储方式存放session信息
 *@Title SessionRedisDao.java
 *@description TODO
 *@time 2020年6月18日 上午10:57:44
 *@author YAOXIONG
 *@version 1.0
 *
 */
public class SessionRedisDao extends EnterpriseCacheSessionDAO  implements SessionDAO {

	private Logger logger = LoggerFactory.getLogger(getClass()); 

	/**
	 * 登录成功的信息存储在 session 的这个 attribute 里.
	 */ 
	private Long timeToLiveSeconds = RedisUtil.timeToLiveSeconds; //session缓存失效时间（单位：秒）

	private String principalId = "principalId";
	
	public SessionRedisDao() { 
		super();
	}
	private RedisTemplate<Object, Object> redisTemplate;

	private RedisConfig redisConfig; 
	public RedisConfig getRedisConfig() {
		return redisConfig;
	}

	public void setRedisConfig(RedisConfig redisConfig) {
		this.redisConfig = redisConfig;
	} 

	/**
	 * 保存session 到 redis中数据
	 * 更新session的最后一次访问时间
	 */
	@Override
	public void update(Session session) throws UnknownSessionException {
		if (session == null || session.getId() == null) {
			return;
		} 
		HttpServletRequest request = ServletUtils.getRequest();
		if (request != null) {
			String uri = request.getServletPath();
			// 如果是静态文件，则不更新SESSION
			if (ServletUtils.isStaticFile(uri)) {
				return;
			} 
		}
		Serializable sessionID = session.getId(); 
		try { 
			// 获取登录者编号
			//用于存储主题主体的会话密钥
			PrincipalCollection pc = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
			String principalId = pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY;
			//查询是否登录信息
			session.setAttribute("principalId",principalId);
			//设置登录信息到REDIS中
			redisConfig.hset(RedisUtil.REDIS_SHIRO_SESSION, sessionID.toString(),
					principalId + "|" + session.getTimeout() + "|" + session.getLastAccessTime().getTime(),session.getLastAccessTime().getTime());

			logger.debug("update {} {}", sessionID, request != null ? request.getRequestURI() : "");

			//设置当前session信息 
			int timeoutSeconds = (int) (session.getTimeout() / 1000);
			//timeToLiveSeconds = timeoutSeconds;
			String sessionKey = RedisUtil.REDIS_SHIRO_SESSION + sessionID.toString();
			redisConfig.set(sessionKey,session,timeToLiveSeconds); 

			if (principalId != null) {
				pc = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
				if (principalId.toString().equals(pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY)) { 
					readPc(sessionID);
				}
			} 
		} catch (Exception e) {
			logger.error("update {} {}", sessionID, request != null ? request.getRequestURI() : "", e);
		} finally {

		}
	}

	@Override
	public void delete(Session session) {
		if (session == null || session.getId() == null) {
			return;
		}
		try { 
			redisConfig.hdel(RedisUtil.REDIS_SHIRO_SESSION, session.getId().toString());
			redisConfig.del(RedisUtil.REDIS_SHIRO_SESSION + session.getId().toString());
			logger.debug("delete {} ", session.getId()); 
			 
		} catch (Exception e) {
			logger.error("delete {} ", session.getId(), e);
		} finally {

		}
	}

	@Override
	public Collection<Session> getActiveSessions() {
		return getActiveSessions(true);
	}

	/**
	 * 获取活动会话
	 * 
	 * @param includeLeave
	 *            是否包括离线（最后访问时间大于3分钟为离线会话）
	 * @return
	 */
	@Override
	public Collection<Session> getActiveSessions(boolean includeLeave) {
		return getActiveSessions(includeLeave, null, null);
	}

	/**
	 * 获取活动会话
	 * 
	 * @param includeLeave
	 *            是否包括离线（最后访问时间大于3分钟为离线会话）
	 * @param principal
	 *            根据登录者对象获取活动会话
	 * @param filterSession
	 *            不为空，则过滤掉（不包含）这个会话。
	 * @return
	 */
	@Override
	public Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession) {
		Set<Session> sessions = Sets.newHashSet(); 
		try { 
			Map<Object, Object> map = redisConfig.hmget(RedisUtil.REDIS_SHIRO_SESSION);
			for (Map.Entry<Object, Object> e : map.entrySet()) {
				if (StringUtils.isNotBlank(e.getKey().toString())) { 
					String[] ss = StringUtils.split(e.getValue().toString(), "|");
					if (ss != null && ss.length == 3) { 
						SimpleSession session = new SimpleSession();
						session.setId(e.getKey().toString());
						session.setAttribute("principalId", ss[0]);
						session.setTimeout(Long.valueOf(ss[1]));
						session.setLastAccessTime(new Date(Long.valueOf(ss[2])));
						try {
							// 验证SESSION
							session.validate(); 
							boolean isActiveSession = false;
							// 不包括离线并符合最后访问时间小于等于3分钟条件。
							if (includeLeave || DateUtils.pastMinutes(session.getLastAccessTime()) <= 3) {
								isActiveSession = true;
							}
							// 符合登陆者条件。
							if (principal != null) {
								PrincipalCollection pc = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
								if (principal.toString().equals(pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY)) {
									isActiveSession = true; 
								}
							}
							// 过滤掉的SESSION
							if (filterSession != null && filterSession.getId().equals(session.getId())) {
								isActiveSession = false;
							}
							if (isActiveSession) {
								sessions.add(session);
							}
						}
						// SESSION验证失败
						catch (Exception e2) {
							redisConfig.hdel(RedisUtil.REDIS_SHIRO_SESSION,e.getKey());
						}
					}
					// 存储的SESSION不符合规则
					else {
						//redisConfig.hdel(RedisUtil.REDIS_SHIRO_SESSION,e.getKey());
					}
				}
				// 存储的SESSION无Value
				else if (StringUtils.isNotBlank(e.getKey().toString())) {
					//redisConfig.hdel(RedisUtil.REDIS_SHIRO_SESSION,e.getKey());
				}
			}
			logger.info("getActiveSessions size: {} ", sessions.size());
		} catch (Exception e) {
			logger.error("getActiveSessions", e); 
		} finally { 
		}
		return sessions;
	}

	/**
	 *DefaultSessionManager 创建完 session 后会调用该方法。
	 * 把 session 保持到 Redis。
	 * 返回 Session ID；主要此处返回的 ID.equals(session.getId())
	 */
	@Override
	protected Serializable doCreate(Session session) {
		logger.warn("=> Create session with ID " + session.getId());
		HttpServletRequest request = ServletUtils.getRequest();
		if (request != null) {
			String uri = request.getServletPath();
			// 如果是静态文件，则不创建SESSION
			if (ServletUtils.isStaticFile(uri)) {
				return null;
			}
		}
	    Serializable sessionId = super.doCreate(session);
		//保存session 
		String sessionKey = RedisUtil.REDIS_SHIRO_SESSION + sessionId.toString();
		redisConfig.set(sessionKey,session,timeToLiveSeconds); 
		
		this.update(session);

		return sessionId;
	}

	/**
	 * 从 Redis 上读取 session，并缓存到本地 Cache. 
	 */
	@Override
	protected Session doReadSession(Serializable sessionId) {
		Session session = null;
		HttpServletRequest request = ServletUtils.getRequest();
		session = super.doReadSession(sessionId); 
		if (request != null) {
			String uri = request.getServletPath();
			// 如果是静态文件，则不获取SESSION
			if (ServletUtils.isStaticFile(uri)) {
				return null;
			}
			session = (Session) request.getAttribute("session_" + sessionId);
		}
		if (session == null) {
			try {  
				session = (Session) redisConfig.get(RedisUtil.REDIS_SHIRO_SESSION + sessionId);
				logger.debug("doReadSession {} {}", sessionId, request != null ? request.getRequestURI() : "");
			} catch (Exception e) {
				logger.error("doReadSession {} {}", sessionId, request != null ? request.getRequestURI() : "", e);
			} finally { 
			}
		}
		//保存在本地数据
		if (request != null && session != null) { 
			request.setAttribute("session_" + sessionId, session); 
		} 
		return session;
	}

	public void readPc(Serializable sessionId) {
		getLoca(sessionId,principalId);
		//getLoca(sessionId,UserUtils.CACHE_MENU_LIST);
		//getLoca(sessionId,UserUtils.CACHE_ROLE_LIST);
	}

	/**
	 * 获取本地数据，从redis中读取并保存到本地
	 *@Title SessionRedisDao.java
	 *@description TODO
	 *@time 2020年6月22日 下午7:25:03
	 *@author YAOXIONG
	 *@version 1.0
	 *
	 */
	public void getLoca(Serializable sessionId,String key)
	{
		Session session = this.doReadSession(sessionId);
		Object obj = null;
		if (session != null) {
			obj = session.getAttribute(key);
			if(obj == null) {
				obj = redisConfig.get(RedisUtil.REDIS_SHIRO_SESSION + key);
			}
		}
		//保存在本地数据
		if (session != null && obj != null) { 
			session.setAttribute(key, obj); 
			redisConfig.set(RedisUtil.REDIS_SHIRO_SESSION + key,obj,timeToLiveSeconds);
		} 
	}



	/**
	 *   考虑到集群的时候退出登录无法删除集群机器本地session故而所有的都从 Redis 读取 Session.
	 */
	@Override
	public Session readSession(Serializable sessionId) throws UnknownSessionException {
		try {
			return this.doReadSession(sessionId);
		} catch (UnknownSessionException e) {
			return null;
		}
	}

	public RedisTemplate<Object, Object> getRedisTemplate() { 
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
		this.redisConfig = new RedisConfig(redisTemplate); 
	} 
}



